iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
JavaScript

可愛又迷人的 Web API系列 第 14

Day14. Web Workers API 的限制與效能優化處理

  • 分享至 

  • xImage
  •  

透過前篇文章可以發現 Web Workers 的強大,我們使用 Web Workers API 讓 JavaScript 可以在瀏覽器中並行執行任務,提升網站效能與使用者體驗。BUT!!!,大家都懂的後面的「但是」才是重點 XXD,儘管 Web Workers 如此厲害,他們還是存在一些限制,以下就跟大家分享 Web Workers 的限制,以及優化要注意的方向。

Web Workers 的限制

無法存取 DOM

Web Workers 的主要限制之一是無法直接存取 DOM,因為 Web Workers 設計理念,是在和主執行緒並行的獨立執行緒中工作,目的當然就是避免阻塞主執行緒,也因為這個特性,他們沒辦法直接操作頁面上的元素或是修改 DOM。

但要較真的話,其實他的限制是 Web Workers 無法「直接」存取 DOM,因為我們還是能透過消息傳遞與主執行緒進行通訊,例如 Web Workers 可以透過postMessage() 方法將資料送到主執行緒,主執行緒再使用 onmessage 事件處理接收資料並更新 DOM。

<!-- 顯示計算結果 -->
<p id="result"></p>

在 main.js 建立一個 Web Worker,透過 onmessage 更新 DOM 元素

const worker = new Worker('worker.js');

// 設定接收來自 Worker 的消息
worker.onmessage = function(event) {
  const data = event.data;
  // 更新 DOM 元素
  document.getElementById('result').textContent = `計算結果: ${data.result}`;
};

// 發送資料到 Worker
worker.postMessage({ number: 42 });

在 worker.js 處理從 main.js 得到的資料數據

// 設定接收來自主執行緒的消息
onmessage = function(event) {
  const number = event.data.number;
  const result = number * number;
  // 將結果發送回主執行緒
  postMessage({ result: result });
};

這種通訊方式雖然是間接的,但可以有效將 Web Workers 的計算與 DOM 操作分離,避免了我們因直接操作 DOM 而造成主執行緒的阻塞。

必須為同源才能互相通訊

我們在用 Shared Workers 時有提到,多個主執行緒必須為同源才能互相通訊。這個限制是出於安全考量,目的是防止惡意代碼從不信任的來源載入到 Web Workers 中,從而保護使用者的安全。

同源政策提升了安全性,但在某些情況下,這些限制可能會帶來一些不便。例如,當需要從不同的來源動態載入腳本或進行跨源請求時,這種限制可能會妨礙應用的靈活性。儘管可以通過 CORS (跨源資源共享) 機制來允許跨源請求,但這種方法在 Web Workers 中的應用仍然受到限制,以下再簡單說明 CORS 在 Web Workers 應用的差異:

對於 HTTP 請求的跨域問題

在 Workers 內部,可以使用 fetch API 或 XMLHttpRequest 進行跨域請求,這些請求需要 Server 端正確設定 CORS 以允許不同來源的請求。

對於 Workers 腳本的跨域問題

CORS 並不會影響 Worker 的同源限制,也就是說 Workers 的腳本本身必須來自與主頁面相同的來源,假設主頁面為 main.js,腳本為 service-worker.js,那這兩個檔案不在同個來源的話,即使 Server 端設定了 CORS,Workers 也無法載入該腳本,這就是 Web Workers 的限制。

解決跨域問題的方法

確保同源

最直接的方法,當然還是確保 Worker 腳本和主頁面在相同的來源,這是最不會有問題的。

使用 CDN

如果要載入外部的腳本,可以考慮將腳本託管在與主頁面相同的來源下,或者使用 CDN 進行部署,確保所有資源都是同源

其他技術

也可以考慮用 JSONP 或讓 Server 端代理處理資源的載入問題。

支援部分 JavaScript API

Web Workers 並不支援所有的 JavaScript API,像是 alertconfirmprompt 這些會跟使用者進行互動的 API,Web Workers 都不支援,因為它們無法在 Worker 執行緒中跳出對話框。此外,Web Workers 也不支援 localStoragesessionStorage 等 Web Storage API,因為這些 API 依賴於存取 DOM 的能力,而我們在前面就提過,Web Workers 沒辦法直接存取 DOM。

因此,在使用 Web Workers 時必須考慮到它的特性,選擇適合的 API 進行開發,Web Workers 最重要的特色就是處理大量的資料數據以及網路請求,因此使用 fetchXMLHttpRequest 等 API,會是能將 Web Workers 功能發揮到淋漓盡致的最好辦法。

效能優化的方向

將複雜的任務進行拆分

在使用 Web Workers 時,將複雜且需密集計算的任務拆分為更小的子任務,可以避免單個 Worker overload,也能提高計算效率。

在 main.js 拆分複雜的任務,並發送給 Worker

const worker = new Worker('chunk.js');

worker.onmessage = function(event) {
  const data = event.data;
  console.log(`接收到來自 Worker 的結果:${data.result}`);
};

// 拆分複雜的任務並發送給 Worker
function processLargeData(data) {
  // 假設我們要處理一個大的數據數組,並將其拆分為多個部分
  const chunkSize = Math.ceil(data.length / 4);
  for (let i = 0; i < 4; i++) {
    const chunk = data.slice(i * chunkSize, (i + 1) * chunkSize);
    worker.postMessage({ chunk: chunk, index: i });
  }
}

const largeData = [/* ... 大量數據 ... */];
processLargeData(largeData);

然後在 chunk.js 將收到的 chunk,透過 processChunk 函式處理,計算結果後再送回主執行緒。

// 設定接收來自主執行緒的消息
onmessage = function(event) {
  const { chunk, index } = event.data;
  // 處理每個子任務的數據
  const result = processChunk(chunk);
  // 將結果發送回主執行緒
  postMessage({ result: result, index: index });
};

function processChunk(chunk) {
  let sum = 0;
  for (let i = 0; i < chunk.length; i++) {
    sum += chunk[i];
  }
  return sum;
}

https://mukiwu.github.io/web-api-demo/img/13-2.png

使用效率較高的數據結構

但我們要處理大量的數據資料,或需要頻繁的傳輸時,使用高效率的數據結構,可以減少序列化和反序列化的開銷,以下分享幾種高效的數據結構方式:

ArrayBufferTypedArray

大家對 ArrayBuffer 應該不陌生,我在前面的章節 [[File API 介紹與實際應用]] 也有簡單提過,他是低階的數據結構,用來表示原始二進制數據的固定長度緩衝區,他不直接操作數據,而是像容器一樣提供儲存空間。

TypedArray 則是用來在 ArrayBuffer 操作數據的 view,等等會用到的 Int32Array 就是 TypedArray 的一種。

// 建立 ArrayBuffer 以及 TypedArray
const buffer = new ArrayBuffer(16);
const intView = new Int32Array(buffer);

// 定義資料
intView[0] = 123;
intView[1] = 456;

// 讀取資料
console.log(intView[0]); // 123

SharedArrayBuffer

SharedArrayBuffer 是一種特殊的 ArrayBuffer,可以在主執行緒和 Web Worker 之間共享。它提供了低延遲的數據交換方式,適合需要在多個執行緒間共享大量數據的場景。

// 主執行緒 (main.js)
const sharedBuffer = new SharedArrayBuffer(1024);
const sharedArray = new Int32Array(sharedBuffer);

// 發送 SharedArrayBuffer 到 Worker
const worker = new Worker('worker.js');
worker.postMessage(sharedBuffer);

// Worker 中 (worker.js)
onmessage = function(event) {
  const sharedArray = new Int32Array(event.data);
  // 修改共享數據
  sharedArray[0] = 789;
};

看完這些數據結構後,大家可能會好奇,那如果用 JSON 傳遞數據呢?

如果是簡單的數據結構,我們的確可以用 JSON 減少轉換的開銷,但如果數據很大,或者數據是二進制,使用 ArrayBuffer 可能是個更好的選擇。

管理 Web Workers 的生命週期

我們需要合理的使用 Worker,盡量重用已有的 Worker 實例,避免不斷建立新的實例;此外,在不需要用到 Worder 時,使用 terminate 方法終止 Worker,以釋放系統資源。

關於進一步的 Web Workers 生命週期介紹,我會在下一篇文章與大家分享,因為這篇又爆字數了 XXD。

小結

好用的工具百百種,但效能優化卻是大家很容易忽略的一點,在使用的同時,也要記得不論是多麽強大的武器,如果不能適當地回收資源與保養,總有一天也是會累垮的。

以上有任何問題,都歡迎留言討論唷。


上一篇
Day13. 使用 Web Workers API 走出自己的路
下一篇
Day15. Web Workers API 的生命週期
系列文
可愛又迷人的 Web API31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言